Что такое providers в NestJS?
Providers в NestJS
Provider — это фундаментальное понятие NestJS, лежащее в основе механизма Dependency Injection (DI). Провайдером может быть сервис, репозиторий, фабрика, хелпер — по сути любой класс или значение, которое нужно внедрить (inject) в другой класс: контроллер, другой provider или модуль.
Главная идея в том, что NestJS сам создаёт экземпляры провайдеров и управляет их жизненным циклом, а разработчику не нужно писать new Service() вручную. Это снижает связанность кода и упрощает тестирование, так как зависимости легко подменить моками.
Как объявить provider
Обычный класс становится провайдером после декоратора @Injectable():
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
findAll() {
return ['Alice', 'Bob'];
}
}
Затем его нужно зарегистрировать в модуле в массиве providers:
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
После регистрации NestJS может внедрить UsersService через конструктор в любой класс того же модуля (или другого, если сервис экспортирован через exports).
Виды провайдеров
Помимо обычных классов, Nest поддерживает кастомные провайдеры с явным указанием токена:
- useClass — указать, какой класс использовать для токена (полезно для подмены реализации по условию, например в тестах).
- useValue — предоставить готовое значение (константу, мок, конфиг-объект).
- useFactory — создать значение через фабричную функцию, которая может принимать другие зависимости.
- useExisting — создать алиас на уже существующий provider.
@Module({
providers: [
{
provide: 'CONFIG',
useFactory: () => ({ apiUrl: process.env.API_URL }),
},
],
})
export class AppModule {}
Область видимости (scope)
По умолчанию providers — синглтоны: один экземпляр на всё приложение, переиспользуемый во всех запросах. Это можно изменить через scope:
Scope.DEFAULT— singleton (по умолчанию);Scope.REQUEST— новый экземпляр на каждый входящий запрос;Scope.TRANSIENT— новый экземпляр при каждом внедрении.
Экспорт провайдеров
Чтобы provider был доступен в другом модуле, его нужно добавить в exports того модуля, где он объявлен, и импортировать сам модуль (imports) в модуль-потребитель.
Зачем это нужно
DI через providers позволяет держать бизнес-логику отдельно от HTTP-слоя (контроллеров), переиспользовать сервисы между модулями и легко подменять зависимости в unit-тестах через Test.createTestingModule и overrideProvider.
Что хочет услышать интервьюер
Понимание, что providers — это механизм Dependency Injection в NestJS
Знание декоратора @Injectable() и обязательной регистрации в массиве providers модуля
Понимание, что по умолчанию provider — singleton в рамках приложения
Знание, что для использования в другом модуле provider нужно экспортировать через exports
Представление о кастомных провайдерах: useClass, useValue, useFactory, useExisting
Пример: Базовый provider и его внедрение в контроллер
// users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private readonly users = ['Alice', 'Bob'];
findAll(): string[] {
return this.users;
}
}
// users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
// Nest сам создаст и подставит экземпляр UsersService
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService], // регистрация провайдера
exports: [UsersService], // делает провайдер доступным для других модулей
})
export class UsersModule {}
Пример: Кастомный provider через useFactory
@Module({
providers: [
{
provide: 'APP_CONFIG',
useFactory: () => ({
apiUrl: process.env.API_URL ?? 'http://localhost:3000',
}),
},
],
exports: ['APP_CONFIG'],
})
export class ConfigModule {}
// Внедрение по строковому токену через @Inject
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class ApiService {
constructor(@Inject('APP_CONFIG') private readonly config: { apiUrl: string }) {}
}
Типичные ошибки
Создают экземпляр сервиса вручную через new вместо использования DI
Забывают добавить сервис в массив providers модуля, из-за чего Nest не может его резолвить
Путают providers и exports — не понимают, что для доступа из другого модуля нужен именно exports
Считают, что каждый inject создаёт новый экземпляр сервиса, не зная про singleton scope по умолчанию
Не знают о существовании кастомных провайдеров (useValue/useFactory) и решают всё через обычные классы


